package com.ease.util {

   import com.ease.util.events.DataEvent;
   import com.ease.util.events.MethodReturnEvent;
   import com.ease.util.logging.LogManager;

   import flash.utils.Dictionary;
   import flash.utils.getDefinitionByName;
   import flash.xml.XMLDocument;

   import mx.logging.ILogger;
   import mx.rpc.xml.SimpleXMLDecoder;

   /**
    * A general purpose deserializer from MXML representation of actionscript object
    * to real actionscript objects. Note that the MXML deserialization
    * requires all class types, in the xml document, to be referenced in the
    * containing application.
    *
    * @example The following example demonstrate the use of MxmlDeserializer:
    *
    * <listing version="3.0">
    *    var serializer:MxmlSerializer = new MxmlSerializer();
    *    var deserializer:MxmlDeserializer = new MxmlDeserializer(classLoader);
    *    //...
    *    // Marshalling actionscript object.
    *    var objectXml:XML = serializer.objectToXml(object);
    *    // ...
    *    // Unmarshalling mxml document to actionscript object.
    *    var object:Object = deserializer.xmlToObject(objectXml);
    *    // ...
    * </listing>
    *
    * If the <code>strict</code> property is false, then this class supports
    * a slight variation of MXML where properties class tags are optional.
    * If a property class tag is not specified, then the object's property
    * class (using reflection) is used.
    * For example, given:
    *
    * <listing version="3.0">
    *    public class PurchaseOrder {
    *       public var amount:Number;
    *       public var address:Address;
    *    }
    *    public class Address {
    *       public var street:String;
    *       public var city:String;
    *    }
    * </listing>
    * A PurchaseOrder could be written as:
    * <pre>
    * &lt; com.test.PurchaseOrder amount="12.99" &gt;
    *    &lt; address city="Palo Alto" /&gt;
    * &lt;/ com.test.PurchaseOrder &gt;
    * </pre>
    */
   public class MxmlDeserializer {

      private static var _logger:ILogger = LogManager.getLogger(MxmlDeserializer);

      /** @private Responsible for locating ad loading classes. */
      protected var _strict:Boolean;
      protected var _objectInitializer:Function;
      protected var _propertyResolver:Function;

      /**
       * The metadata used for serialization and deserialization.
       */
      private var _metadata:MxmlMetadata = new MxmlMetadata;

      /**
       * Cache for the type information of a property. The key is
       * a combination of both: class + property names.
       */
      private var _propertyTypesByName:Dictionary;

      /**
       * The actual object references found so far during deserialize.
       * Map an id (uint or string) to object.
       */
      private var _referencesById:Dictionary;

      /**
       * @param strict Whether the Mxml parsing is strict.
       * If false then optional class name tags are supported.
       *
       * @param objectInitializer <code>function(object:Object):void</code>
       * Called with each object instance created in <code>xmlToObject</code> to
       * initialize it.
       *
       * @param propertyResolver Called to set an object property, possibly
       * after value conversions.
       * <code>function(object:Object, propName:String, propValue:XML):Boolean</code>
       * Where object is the object being deserialized, propName is the property
       * name to be set, and propValue is the value specified in the xml.
       * Returns true if this method assigned the property value.
       */
      public function MxmlDeserializer(loader:IClassLoader = null,
                                       strict:Boolean = false,
                                       objectInitializer:Function = null,
                                       propertyResolver:Function = null) {
         _metadata.classLoader = loader;
         _strict = strict;
         _objectInitializer = objectInitializer;
         _propertyResolver = propertyResolver;

         // This will initialize with empty dictionaries.
         clear();
      }

      /**
       * The class loader instance used to retrieve type information when deserializing
       * objects.
       */
      public function get classLoader():IClassLoader {
         return _metadata.classLoader;
      }

      /**
       * Converts the given MXML document into an equivalent
       * actionscript object.
       *
       * @param parentNode
       *    root node of the MXML document.
       * @param className
       *    The object's class name, or null if the name should be retrieved
       *    from the parentNode tag name.
       * @param references
       *    A dictionary which maps ids (uint) to already existing objects.
       * @return
       *    object constructed from the passed in MXML document.
       * @throws Error
       *    Any error happening during deserialization is thrown as <code>Error</code>
       *    object.
       */
      public function xmlToObject(parentNode:XML, className:String = null,
                                  references:Dictionary = null):Object {
         Diagnostics.assert(_metadata.isDefaultClassLoader(),
            'xmlToObject will only work on default classloader.  ' +
            'Use xmlToObjectAsync for custom classloader. ');

         initializeReferences(references);

         var context:ThreadContext = new ThreadContext();
         context.addWorkItem(
            new WorkItem_ProcessAndAssign(parentNode, className, context, "result"));
         context.addWorkItem(new WorkItem_LoadMetadata(parentNode));

         // Passing 0 as work interval will make the thread synchronous
         var thread:PseudoThread =
            new PseudoThread(0, 1, processWorkItems, context, "xmlToObject");

         context.thread = thread;

         // Start the thread in this frame. Upon completion the result will be in the
         // thread context.
         thread.startNow();

         // At this point thread execution is complete.
         if (context.error != null) {
            // This is sync method => report errors via exception.
            throw context.error;
         }

         return context.result;
      }

      /**
       * Converts the given MXML document into an equivalent
       * actionscript object.
       *
       * @param parentNode
       *    root node of the MXML document.
       * @param className
       *    The object's class name, or null if the name should be retrieved
       *    from the parentNode tag name.
       * @param references
       *    A dictionary which maps ids (uint) to already existing objects.
       * @param callback
       *    The callback for handling the results.  The signature of the callback
       *    should accept MethodReturnEvent as parameter.
       *    Example: function myCallback(event:MethodReturnEvent):void {...}
       * @param callContext
       *    The context information to restore in the callback function.
       * @param chunk
       *    If true the processing of the workitems will be lauched using timer. Otherwise,
       *    the processing of workitems will be sequential.
       */
      public function xmlToObjectAsync(parentNode:XML,
                                       className:String, references:Dictionary,
                                       callback:Function, callContext:Object = null,
                                       chunk:Boolean = false):void {
         if (callback == null) {
            throw new ArgumentError("Callback is required.");
         }

         initializeReferences(references);

         var context:ThreadContext = new ThreadContext(callback, callContext);
         context.chunk = chunk;

         context.addWorkItem(
            new WorkItem_ProcessAndAssign(parentNode, className, context, "result"));
         context.addWorkItem(new WorkItem_LoadMetadata(parentNode));

         var thread:PseudoThread = new PseudoThread(
            (chunk? 20 : 0), 1, processWorkItems, context, "xmlToObjectAsync");

         context.thread = thread;

         var onThreadTerminated:Function = function(event:DataEvent):void {
            var c:ThreadContext = event.data as ThreadContext;
            c.thread.removeEventListener(
               PseudoThread.THREAD_TERMINATED_EVENT, arguments.callee);
            c.callback(new MethodReturnEvent(c.result, c.error, c.callContext));
         }

         thread.addEventListener(PseudoThread.THREAD_TERMINATED_EVENT, onThreadTerminated);

         // Start the thread on the next frame.
         thread.start(!chunk);
      }

      /**
       * A general work item dispatcher. It mostly inspects the type of work item
       * we need to process and dispatches to other methods.
       */
      private function processWorkItems(context:ThreadContext):Boolean {
         if (context.error != null) {
            // Error processing the request
            return true;
         }

         var workItem:Object = context.nextWorkItem();
         if (workItem == null) {
            return true; // work completed.
         }

         if (workItem is WorkItem_ProcessAndAssign) {
            // This will process the current work item and push some more if needed.
            return xmlToObjectInternal(context);
         }

         if (workItem is WorkItem_Assign) {
            return processAssign(context);
         }

         if (workItem is WorkItem_LoadingType) {
            return processTypeLoading(context);
         }

         if (workItem is WorkItem_LoadMetadata) {
            return processLoadMetadata(context);
         }

         return false; // work not completed
      }

      /**
       * Called to load the metadata.
       */
      private function processLoadMetadata(context:ThreadContext): Boolean {
         // Loading the metadata.
         var wi:WorkItem_LoadMetadata = context.workItem as WorkItem_LoadMetadata;
         _metadata.load(wi.node);

         // Need to continue loading the objects.
         return false;
      }

      /**
       * Process and "assign" work item.
       */
      private function processAssign(context:ThreadContext): Boolean {
         // Just assign the property to the host, do nothing else.
         // Generate no new work items.
         WorkItem_Assign(context.workItem).execute();
         return false; // work not completed
      }

      /**
       * Process a "type loading" work item.
       */
      private function processTypeLoading(context:ThreadContext): Boolean {
         var wiLoading:WorkItem_LoadingType = context.workItem as WorkItem_LoadingType;
         if (wiLoading.isCompleted) {
            // Loading is complete. If successful we are good to continue with processing
            // other work items. Else we exit with error because type cannot be loaded
            // for some reason.
            if (wiLoading.error != null || wiLoading.result == null) {
               if (wiLoading.error != null) {
                  context.error = wiLoading.error;
               } else {
                  // No error, but null result. Report our own error
                  context.error = new ReferenceError(
                     "MxmlDeserializer cannot load type definition for " +
                     wiLoading.typeName);
               }
               return true;// work completed, but with error.
            }

            // Loading type is successful, need to continue processing.
         } else {
            // If we are running in sync mode then pause the thread until
            // the class is loaded.
            if (!context.chunk) {
               context.thread.isSuspended = true;
            }
            // Loading not completed. Push back this item on top of the work items stack.
            context.addWorkItem(wiLoading);
         }

         return false;
      }

      /**
       * The actual work-horse method that does the deserialization.
       */
      private function xmlToObjectInternal(context:ThreadContext):Boolean {
         var wi:WorkItem_ProcessAndAssign = context.workItem as WorkItem_ProcessAndAssign;
         var node:XML = wi.node;

         if (node == null) {
            // _logger.debug("[xmlToObject] Null XML node.");
            wi.setResult(null);
            return false;
         }

         // If the class name was specified in advance, this means that the parentNode
         // contains the data directly in the root object.
         var className:String = wi.className;
         if (className == null) {
            className = node.name();
            wi.className = className;
         }

         var object:Object;
         var isSimpleType:Boolean = MxmlUtil.isSimpleType(className);
         var isContainerType:Boolean = MxmlUtil.isContainerType(className);
         if (isSimpleType || isContainerType) {
            object = instantiateObject(node, className);
            if (isSimpleType) {
               // No further processing for simple types. Instantiating is enough.
               wi.setResult(object);
               return false;
            }
            // Push a new work item to assign the container once all if its
            // children are processed.
            context.addWorkItem(wi.newAssign(object));
            // For containers we also do a separate processing and then return.
            processContainers(object, node, context);
            return false;
         }

         object = findReferencedObject(node);
         if (object == null) {
            object = wi.instance;
            if (object == null) {
               // Push back the current work item. We cannot create an object from
               // its type right now.
               context.addWorkItem(wi);

               // Start a loading process and add a work item to wait for the load to complete.
               context.addWorkItem(new WorkItem_LoadingType(className, _metadata.classLoader,
                  _metadata.getLoadInfo(node.@[MxmlUtil.ATTRIB_INFO_REF]),
                  function (result:Object, error:Error, wi:Object):void {
                     context.error = error;
                     wi.instance = result;
                     context.thread.isSuspended = false;
                  },
                  wi
               ));
               return false;
            }
            objectInstantiated(object, node);
         }

         // We have created the object. We will assign it to the right place
         // after we process all of its properties first.
         context.addWorkItem(wi.newAssign(object));

         // Not a simple type, not a container. We process its properties
         // information - if any.
         processProperties(object, node, className, context);

         // We still have work to do, return false.
         return false;
      }

      /**
       * A helper method to properly handle object attributes.
       * Process only properties defined in the type definition of an object.
       * Important to know is that many extension points data rely on treating
       * attributes as properties during deserialization.
       */
      private function processProperties(object:Object, node:XML,
                                         className:String,
                                         context:ThreadContext):void {
         var properties:XMLList = node.attributes() + node.children();
         for each (var propertyXml:XML in properties) {
            var propertyName:String = String(propertyXml.name());

            // We ignore attribute or properties which match our reserved values.
            if (MxmlUtil.isReservedAttribute(propertyName)) {
               continue;
            }

            var type:String = getDeclaredPropertyType(object, className, propertyName);
            setPropertyXml(object, className, propertyXml, context, type);
         }
      }

      /**
       * A helper method which creates the object from the XML description.
       * Note that it does not really process its sub XML info, but just based on the
       * top XML tag instantiates it.
       */
      private function instantiateObject(parentNode:XML, className:String):Object {
         // If we find that the current object is a reference and
         // is already available, we return the referenced object.
         var object:Object = findReferencedObject(parentNode);
         if (object != null) {
            return object;
         }

         var isSimpleType:Boolean = MxmlUtil.isSimpleType(className);
         var isContainerType:Boolean = MxmlUtil.isContainerType(className);
         Diagnostics.assert(isSimpleType || isContainerType,
            "instantiateObject should be called only with simple or container types.");

         var clazz:Class = getDefinitionByName(className) as Class;

         if (isSimpleType) {
            // The call below returns an object from the XML node. e.g. If it receives the
            // following XML node: <String>22</String> you will get back an object with
            // value 22. In order to make it the right type we need the "new  clazz(...)"
            // call which in the example will look like this:
            //   object = new String(22)
            // so you will get the right type String instead of int which will be the
            // result from the decoder.decodeXML()
            // The fact is that since we do not really save properties which value is null,
            // we never worry about new null() or new undefined(), cause we never get
            // back something like <null></null> or <undefined></undefined>, so we
            // cannot really fall into this case
            var obj:Object = processSimpleType(parentNode, className);
            object = new clazz(obj);
            return object;
         }

         // NOTE: Create the new object and put it in the _references array
         // right away to avoid stack problems during circular references.
         object = new clazz();

         objectInstantiated(object, parentNode);

         return object;
      }

      private function objectInstantiated(object:Object, parentNode:XML):void {
         addReferencedObject(object, parentNode);

         if (_objectInitializer != null) {
            _objectInitializer(object);
         }
      }

      /**
       * A helper method to handle simple types deserialization. There are certain
       * expectations how <code>node</code> is expected to look like for the basic
       * types:
       * <ul>
       *  <li>className = "String"
       *      node = <object>some text goes here</object>
       *  </li>
       *  <li>className = "int"
       *      node = <object>22</object>
       *  </li>
       *  <li>className = "Boolean"
       *      node = <object>false</object>
       *  </li>
       *  <li>className = "XML"
       *      node = <object><topTag>...</topTag></object>
       *  </li>
       * </ul>
       * <object/> in the examples above is a root tag and in some cases it will be
       * the type name. e.g.: node = <String>22</String>
       *
       * <p/> Since the root node name is inspected in callers of this method,
       * it is ignored here. We mainly pass it for fast lookup for the once case we
       * need it: className == "XML".
       */
      private function processSimpleType(node:XML, className:String):Object {
         if (className == "XML") {
            // We already have the child as XML, so no need to actually do
            // anything more here. In fact, if we execute the "else" part here
            // the returned object will be XML but messed-up (all of its subdata missing).
            return node.children()[0];
         }

         if (MxmlUtil.isNullNode(node)) {
            // This is how the node looks like: <propName>MxmlUtil.NULL</propName>
            return null;
         }

         var xmlDoc:XMLDocument = new XMLDocument(node.toXMLString());
         var decoder:SimpleXMLDecoder = new SimpleXMLDecoder();
         Diagnostics.assert(xmlDoc.childNodes.length == 1,
            "According to our plans a document should have exactly one child");
         return decoder.decodeXML(xmlDoc.firstChild);
      }

      /**
       * A helper method to handle container types deserialization.
       */
      private function processContainers(object:Object, node:XML,
                                         context:ThreadContext):void {
         if (object is Array) {
            processArray(object as Array, node, context);
         } else if (object is Dictionary) {
            processDictionary(object as Dictionary, node, context);
         } else {
            processObjectDictionary(object, node, context);
         }
      }

      /**
       * A helper method to deserialize an array. At this point <code>node</code>
       * is expected to look like this:
       * node = <Array>
       *          <type1>val1</type1>
       *          ...
       *          <type1>valN</type1>
       *        </Array>
       * or if the type is not specified, the array values need to be in a root tag
       * node = <object>
       *          <type1>val1</type1>
       *          ...
       *          <type1>valN</type1>
       *        </object>
       * In the example above <object/> is the root tag, but any root is good enough.
       */
      private function processArray(array:Array, node:XML,
                                    context:ThreadContext):void {
         var index:uint = 0;
         for each(var item:XML in node.children()) {
            // Put a dummy item in the array. Will add a new item if needed
            // or reuse existing index if array already has items. The last situation
            // may happen if object is being reused because of not calling "clear"
            // method between two subsequent deserialization operations.
            if (index >= array.length) {
               array.push("replaceMe");
            }

            // Prepare a work item to fill-up this later when processed
            context.addWorkItem(new WorkItem_ProcessAndAssign(item, null, array, index));
            index += 1;
         }
      }


      /**
       * A helper method to deserialize dictionary. At this point <code>node</code>
       * is expected to look like this:
       * node = <Dictionary>
       *          <keyAndValue>
       *             <KeyType>key1</KeyType>
       *             <ValueType>val1</ValueType>
       *          </keyAndValue>
       *          ...
       *          <keyAndValue>
       *             <KeyType>keyN</KeyType>
       *             <ValueType>valN</ValueType>
       *          </keyAndValue>
       *        </Dictionary>
       * or if the type is not specified, the dictionary values need to be in a root tag
       * node = <object>
       *          <keyAndValue>
       *             <KeyType>key1</KeyType>
       *             <ValueType>val1</ValueType>
       *          </keyAndValue>
       *          ...
       *          <keyAndValue>
       *             <KeyType>keyN</KeyType>
       *             <ValueType>valN</ValueType>
       *          </keyAndValue>
       *        </object>
       * In the example above <object/> is the root tag, but any root is good enough.
       */
      private function processDictionary(dict:Dictionary, node:XML,
                                         context:ThreadContext):void {
         for each(var child:XML in node.children()) {
            var keyValue:XMLList = child.children();
            var keyNode:XML = keyValue[0];   // first child is always the key
            var valueNode:XML = keyValue[1]; // second child is the value

            var wiValue:* = new WorkItem_ProcessAndAssign(valueNode, null, dict);
            context.addWorkItem(wiValue);

            // This work item will assign the deserialized keyNode as "propKey"
            // property of the upper wiValue work item. This way when the wiValue
            // work item is processed, we will have the correct dictionary key
            // where to set it.
            var wiKey:* = new WorkItem_ProcessAndAssign(keyNode, null, wiValue, "propKey");
            context.addWorkItem(wiKey);
         }
      }

      /**
       * A helper method to deserialize a plain old Object dictionary. At this point the
       * <code>node</code> is expected to look like
       * node = <Object>
       *          propName1>...</propName1>
       *          ...
       *          <propNameN>...</propNameN>
       *        </Object>
       * or if the type "Object" is not specified, the object properties need to be
       * in a root tag
       * node = <root>
       *          <propName1>...</propName1>
       *          ...
       *          <propNameN>...</propNameN>
       *        </root>
       */
      private function processObjectDictionary(object:Object, node:XML,
                                               context:ThreadContext):void {
         for each(var child:XML in node.children()) {
            // At this point child = <propName>...</propName>
            var key:String = MxmlUtil.unescape(child.name());
            var value:Object = null;
            if (child.hasSimpleContent()) {
               // if we have <propName>value</propName> or just <propName></propName>
               value = processSimpleType(child, null);
               object[key] = value;
            } else {
               // if we have <propName><TypeName>...</TypeName></propName>
               context.addWorkItem(
                  new WorkItem_ProcessAndAssign(child.children()[0], null, object, key));
            }
         }
      }

      /**
       * Extracts properties out of the passed in XML node and applies
       * them to the supplied source object.
       *
       * @param object
       *    Extracted properties are set on this object.
       * @param objectClassName
       *    The class name of the object. Will be used for faster look-up
       *    together with the property name from a cached information for
       *    each property.
       * @param propertyXml
       *    The XML document representing the source objects properties/attributes.
       * @param propertyTypeName
       *    The type of the property (usually the declared type of the property).
       */
      private function setPropertyXml(object:Object, objectClassName:String,
                                      propertyXml:XML, context:ThreadContext,
                                      propertyTypeName:String, propertyValue:Object = null):void {
         try {
            var propertyName:String = String(propertyXml.name());

            if (_propertyResolver != null) {
               var isPropertyResolved:Boolean =
                  _propertyResolver(object, propertyName, propertyXml);
               if (isPropertyResolved) {
                  return;
               }
            }

            if (MxmlUtil.isNullNode(propertyXml)) {
               // This is how the node looks like: <propName>MxmlUtil.NULL</propName>
               object[propertyName] = null;
               return;
            }

            var type:String = propertyTypeName;
            if (MxmlUtil.isSimpleType(type)){
               if (type == "Boolean") {
                  propertyValue = (propertyXml.toString() == "true") ? true : false;
               } else if (type == "XML") {
                  // We need to get rid of the property name.
                  // This is how the propertyXml node is expected to look at this point.
                  //  <propName>
                  //    <top> <-- this is propertyXml.children()[0]
                  //      <mid>
                  //        <low>lowValue</low>
                  //      </mid>
                  //    </top>
                  //  </propName>
                  propertyValue = new XML(propertyXml.children()[0]);
               } else if (type == "String") {
                  // toString() returns the XML value in this case (wander why?)
                  // even if you have <propName>some string value</propName>
                  propertyValue = propertyXml.toString();
               } else {
                  // toString() returns the XML value in this case (wonder why?).
                  // even if you have <propName>value</propName>
                  var clazz:Class = getDefinitionByName(type) as Class;
                  propertyValue = new clazz(propertyXml.toString());
               }
            } else if (MxmlUtil.isContainerType(type)) {
               if (type == "Array") {
                  if (isPropertyClassSpecified(propertyXml, "Array")) {
                     //  <intsData>
                     //    <Array mxmlObjId="2">
                     //       <int>10</int>
                     //    </Array>
                     //  </intsData>
                     context.addWorkItem(new WorkItem_ProcessAndAssign(
                        propertyXml.children()[0], null, object, propertyName));
                     return;
                  } else {
                     // or the backward compatible approach:
                     //  <intsData>
                     //    <int>10</int>
                     //  </intsData>
                     propertyValue = [];
                     processContainers(propertyValue, propertyXml, context);
                  }
               } else if (type == "Object") {
                  if (_strict || isPropertyClassSpecified(propertyXml)) {
                     context.addWorkItem(new WorkItem_ProcessAndAssign(
                        propertyXml.children()[0], null, object, propertyName));
                     return;
                  } else {
                     if (propertyXml.hasSimpleContent()) {
                        // For simple property values, e.g:
                        // <propName>some simple string value</propName> directly
                        // convert to string. This is for convenience purposes for
                        // people writing MXML by hand.
                        propertyValue = propertyXml.toString();
                     } else {
                        // For more complex property values, e.g.:
                        // <propName><subProp>value</subProp></propName> consider
                        // processing the Object as a container.
                        propertyValue = {}
                        processContainers(propertyValue, propertyXml, context);
                     }
                  }
               } else {
                  // flash.util.Dictionary or flash.util::Dictionary
                  if (_strict || isPropertyClassSpecified(propertyXml)) {
                     context.addWorkItem(new WorkItem_ProcessAndAssign(
                        propertyXml.children()[0], null, object, propertyName));
                     return;
                  } else {
                     propertyValue = new Dictionary();
                     processContainers(propertyValue, propertyXml, context);
                  }
               }
            } else {
               var wi:WorkItem_ProcessAndAssign;
               if (_strict || isPropertyClassSpecified(propertyXml)) {
                  wi = new WorkItem_ProcessAndAssign(
                     propertyXml.children()[0], null, object, propertyName);
               } else {
                  wi = new WorkItem_ProcessAndAssign(
                     propertyXml, type, object, propertyName);
               }
               context.addWorkItem(wi);
               return;
            }
            object[propertyName] = propertyValue;
         } catch (error:Error) {
            // TODO harish: this and all errors should be returned in the
            // AsyncCallBack that will be passed to xmlToObject in the future.
            _logger.error("setPropertyXml found error: {0}", error.message);
         }
      }

      /**
       * Checks whether the given property xml specifies a class name.
       * A class is considered to be specified if there is exactly 1
       * child tag whose name contains . or ::
       * @param expectedType
       *    Sometimes, besides knowing if a property type is specified, you can be
       *    interested whenter it is a particular type. Use this parameter to specify
       *    whether the property is of particular type. So if you specify this parameter
       *    the method works more like a "lookupPropertyClass".
       */
      private function isPropertyClassSpecified(propertyXml:XML,
                                                expectedType:String = null):Boolean {
         if (propertyXml.hasSimpleContent()) {
            return false;
         }
         var children:XMLList = propertyXml.children();
         if (children.length() != 1) {
            return false;
         }
         var childTagName:String = children[0].name();
         if (childTagName.indexOf(".") > 0
            || childTagName.indexOf("::") > 0
            || MxmlUtil.isSimpleType(childTagName)
            || MxmlUtil.isContainerType(childTagName)
            || MxmlUtil.isGlobalType(childTagName)) {
            if ((expectedType != null) && (childTagName != expectedType)) {
               return false;
            } else {
               return true;
            }
         }
         /* if there is only 1 property specified in the xml, and the property
         is of complex type with a default-package class name, then we
         would wrongly return false, and the developer in this case would get a
         "class x doesn't have property y" error.*/
         return false;
      }

      /**
       * An utility method to get the type of a property. Return it in
       * <i>normalized</i> form.
       *
       * @see com.ease.flexutil.MixedUtil#normalizeClassName
       */
      private function getDeclaredPropertyType(object:Object, objectClassName:String,
                                               propertyName:String):String {
         // speev: This cache speeds up the deserialization in cases where
         // types repeat often (e.g. in my tests > 50% speed-up)
         var key:String = objectClassName + "." + propertyName;
         var type:String = _propertyTypesByName[key] as String;
         if (type == null) {
            type = ReflectionUtil.getDeclaredPropertyTypeName(propertyName, object);
            type = MixedUtil.normalizeClassName(type);
            if (objectClassName != null) {
               _propertyTypesByName[key] = type;
            }
         }
         return type;
      }

      /**
       * Helper to correctly initialize the serializer internal reference dictionary.
       */
      private function initializeReferences(incomingReferences:Dictionary):void {
         if (incomingReferences != null) {
            // Copy the dictionary into the internal one.
            MixedUtil.copyDictionary(incomingReferences, _referencesById);
         }
      }


      /**
       * Searches a XML node in the dictionary of already created objects.
       * Of course this is done only of the XML node has an appropriate tag
       * saying that it is actually a reference to a different node.
       */
      private function addReferencedObject(object:Object, node:XML):void {
         // NOTE: Treat ID and REF records alike.
         var ids:XMLList = node.@[MxmlUtil.ATTRIB_ID];
         if (ids.length() == 0) {
            ids = node.@[MxmlUtil.ATTRIB_REF];
         }

         if (ids.length() > 0) {
            var key:XML = ids[0];
            _referencesById[key.toString()] = object;
         }
      }

      /**
       * Searches a XML node in the dictionary of already created objects.
       * Of course this is done only of the XML node has an appropriate tag
       * saying that it is actually a reference to a different node.
       */
      private function findReferencedObject(xmlObject:XML):Object {
         var retVal:Object = null;
         var refs:XMLList = xmlObject.@[MxmlUtil.ATTRIB_REF];

         // NOTE: Treat ID and REF records alike.
         if (refs.length() == 0) {
            refs = xmlObject.@[MxmlUtil.ATTRIB_ID];
         }

         if (refs.length() > 0) {
            // The key is usually uint or string
            var key:XML = refs[0];
            retVal = _referencesById[key.toString()];
         }

         return retVal;
      }

      /**
       * Clears any state this instance may be maintaining, e.g. IDs of the
       * objects deserialized so far.
       * @internal
       * Clear the internal map where for each deserialized object an id is kept.
       * This map helps tracking multiple references to each object
       * This map exists during the lifetime of the <code>MxmlDeserializer</code>
       * object. You may want to clear it between different calls to
       * <code>xmlToObject</code> if you keep the instance around.
       * Keeping the internal reference around is useful if you serialize a
       * few objects in a row with references to each other or common reference to
       * a third party objects and then deserialize them in the same sequence.
       */
      public function clear():void {
         _referencesById = new Dictionary();
         _propertyTypesByName = new Dictionary();
      }

   } // class
} // package

import com.ease.util.AsyncCallback;
import com.ease.util.IClassLoader;
import com.ease.util.PseudoThread;

/**
 * The bunch of data that makes it possible that MxmlDeserializer works in chunks.
 * We call the chunks - "work items".
 */
class ThreadContext {
   public var thread:PseudoThread;
   public var chunk:Boolean;
   public var callback:Function;
   public var callContext:Object;
   public var result:Object;

   [InjectableError]
   public var error:Error;

   /**
    * A stack with all working items.
    */
   private var _workItems:Array = [];

   /**
    * The  current work item.
    */
   private var _wi:Object = null;

   /**
    * Initialize an instance of this object.
    */
   public function ThreadContext(callback:Function = null,
                                 callContext:Object = null):void {
      this.callback = callback;
      this.callContext = callContext;
   }

   /**
    * Advanced to the next work item. The work items work like a stack. The last
    * pushed is the first processed.
    */
   public function nextWorkItem():Object {
      _wi = _workItems.length > 0 ? _workItems.pop() : null;
      return _wi;
   }

   /**
    * Push a work item on top of the work items stack.
    */
   public function addWorkItem(wi:Object):void {
      _workItems.push(wi);
   }

   /**
    * The current work item.
    */
   public function get workItem():Object {
      return _wi;
   }
}

/**
 * This work item has the following meaning: process the node, create an object from
 * it and assign the created object as property to <code>propHost</code>
 * under the name <code>propKey</code>. Note that <code>propKey</code> can be
 * actually a string, int or any object (e.g. if we are assigning an object to a
 * dictionary then the key can be an object)
 */
class WorkItem_ProcessAndAssign {
   public var node:XML = null;
   public var className:String = null;
   public var instance:Object = null;
   public var propHost:Object = null;
   public var propKey:* = null;

   public function WorkItem_ProcessAndAssign(
      node:XML, className:String = null,
      propHost:Object = null, propKey:* = null):void {
      this.node = node;
      this.className = className;
      this.propHost = propHost;
      this.propKey = propKey;
   }

   public function setResult(value:Object = null):void {
      var retVal:Boolean = false;
      if (propHost != null) {
         propHost[propKey] = value;
         retVal = true;
      }
   }

   public function newAssign(propValue:Object):WorkItem_Assign {
      return new WorkItem_Assign(propHost, propKey, propValue);
   }
}

/**
 * This work item has the following semantics: assign <code>propValue</code> as
 * property to <code>propHost</code> under the property name <code>propKey</code>
 *
 * Why needed, sometimes while we process the WorkItem_ProcessAndAssign we do not
 * want to inject the object we create instantly in the <code>propHost</code> as
 * property. We need to process all of its sub-items first and then assign the property
 * when it is fully constructed (all of its sub-properties are assigned).
 * Therefore we construct this "assign" work item and push it in the work items stack
 * before we push the sub-properties processing work items.
 */
class WorkItem_Assign {
   public var propHost:Object = null;
   public var propKey:* = null;
   public var propValue:* = null;

   public function WorkItem_Assign(propHost:Object = null,
                                   propKey:* = null, propValue:* = null):void {
      this.propHost = propHost;
      this.propKey = propKey;
      this.propValue = propValue;
   }

   // We just ignore the passed parameter.
   public function execute():void {
      var retVal:Boolean = false;
      if (propHost != null) {
         propHost[propKey] = propValue;
         retVal = true;
      }
   }
}

/**
 * This work item starts a type loading request when constructed and then
 * is used to figure out whether the process is completed. If the type loading is
 * not completed we should wait until it is completed.
 */
class WorkItem_LoadingType {
   public var result:Object = null;
   public var error:Error =  null;
   public var typeName:String = "";
   public var callback:Function;
   public var callContext:Object;
   private var _loadComplete:Boolean = false;

   public function WorkItem_LoadingType(typeName:String,
                                        loader:IClassLoader,
                                        loadInfo:Object = null,
                                        callback:Function = null,
                                        callContext:Object = null):void {
      this.typeName = typeName;
      this.callback = callback;
      this.callContext = callContext;

      if (loader != null) {
         loader.instantiate(typeName, new AsyncCallback(onResult, onFault), loadInfo);
      } else {
         throw new ReferenceError(
            "MxmlDeserializer cannot load type definition for " + typeName);
      }
   }

   public function onResult(result:Object):void {
      this.result = result;
      _loadComplete = true;

      if (result == null) {
         onFault(new ReferenceError("Failed to load class " + typeName));
         return;
      }

      // Invoke the callback if any
      if (callback != null) {
         callback(result, null, callContext);
      }
   }

   public function onFault(error:Error):void {
      this.error = error;
      _loadComplete = true;

      // Invoke the callback if any
      if (callback != null) {
         callback(null, error, callContext);
      }
   }

   public function get isCompleted():Boolean {
      return _loadComplete;
   }
}

/**
 * This workitem will start loading of the metadata for the serialized object.
 * The metadata is used for loading the object and its descendants type reference.
 */
class WorkItem_LoadMetadata {
   public var node:XML = null;

   public function WorkItem_LoadMetadata(node:XML):void {
      this.node = node;
   }
}
